import { computed, effectScope, getCurrentInstance, inject, isRef, reactive, toRefs } from "vue";
import { piniaSymbol } from "./rootStore.js";

function isObject(obj) {
  return obj !== null && typeof obj === 'object';
}


/**
 * 创建一个选项存储
 *
 * 该函数用于根据提供的配置选项创建一个具有状态、getter和action的存储
 * 它使用Pinia作为状态管理库
 *
 * @param {string} id - 存储的唯一标识符
 * @param {Object} options - 包含状态、getter和action的配置对象
 * @param {Object} pinia - Pinia状态管理实例
 */
function createOptionStore(id, options, pinia) {
  // 解构配置选项，获取状态、getter和action
  const {state, getters = {}, actions = {}} = options;

  /**
   * 存储配置函数
   *
   * 该函数负责在Pinia实例中设置存储的状态，并将状态、getter和action整合到一个对象中
   * 它使用计算属性来动态计算getter的值，并确保可以访问本地状态和actions
   *
   * @returns {Object} 整合了状态、getter和action的存储对象
   */
  function setup() {
    // 在Pinia的状态值中设置当前存储的状态
    pinia.state.value[id] = state ? state() : {};
    // 将当前存储的状态转换为响应式引用
    const localState = toRefs(pinia.state.value[id]);
    // 整合本地状态、actions和计算后的getter到一个对象中
    return Object.assign(
      localState,
      actions,
      Object.keys(getters || {}).reduce((gettersObj, getterName) => {
        // 创建计算属性，用于动态计算getter的值
        gettersObj[getterName] = computed(() => {
          // 获取当前存储的实例
          const store = pinia._s.get(id);
          // 执行getter
          return getters[getterName].call(store);
        });
        return gettersObj;
      }, {})
    );
  }

  // 使用setup函数创建存储
  const store = createSetupStore(id, setup, pinia);

  /**
   * 重置存储状态的方法
   *
   * 该方法用于将存储的状态重置为初始状态
   * 它通过调用$patch方法，将状态更新为新的初始状态
   */
  store.$reset = function () {
    // 获取新的初始状态
    const newState = state ? state() : {};
    // 使用新的初始状态更新存储的状态
    store.$patch(newState);
  };
}


/**
 * 合并两个对象的属性和嵌套属性
 * 此函数递归地合并源对象（partialState）到目标对象（target）中
 * 如果遇到嵌套对象，且该对象不是引用类型，则递归合并；否则，直接覆盖
 *
 * @param {Object} target - 目标对象，将被源对象的属性和值更新
 * @param {Object} partialState - 源对象，其属性和值将被合并到目标对象中
 * @returns {Object} 返回更新后的目标对象
 */
function merge(target, partialState) {
  // 遍历源对象的每个属性
  for (const key in partialState) {
    // 获取当前属性的新状态
    const newState = partialState[key];
    // 获取当前属性的旧状态
    const oldState = target[key];
    // 如果新旧状态都是对象且新状态不是引用类型，则递归合并
    if (isObject(newState) && isObject(oldState) && !isRef(newState)) {
      target[key] = merge(oldState, newState);
    } else {
      // 否则，直接用新状态覆盖旧状态
      target[key] = newState;
    }
  }
  // 返回更新后的目标对象
  return target;
}


/**
 * 创建并设置存储（stores）函数
 * 该函数用于初始化一个存储，并将其绑定到Pinia实例中
 * @param {String} id - 存储的唯一标识符
 * @param {Function} setup - 用于设置存储状态和操作的函数
 * @param {Object} pinia - Pinia实例
 * @returns {Object} - 返回初始化后的存储对象
 */
function createSetupStore(id, setup, pinia) {
  // 创建一个响应式存储对象，并定义$patch方法用于更新存储状态
  const store = reactive({
    $patch(partialState) {
      merge(pinia.state.value[id], partialState);
    },
  });
  let scope;

  // 包装存储中的操作，使其能够在执行时绑定正确的上下文
  function wrapAction(action) {
    return function () {
      let result = action.call(this, ...arguments);
      return result;
    };
  }

  // 使用Pinia的effect运行存储的设置函数，确保存储在正确的作用域内运行
  // 这里创建并运行一个作用域，用于管理存储内部的状态和操作
  const setupStore = pinia._e.run(() => {
    scope = effectScope();
    return scope.run(() => {
      return setup();
    });
  });

  // 遍历设置存储的结果，将其中的操作进行包装
  for (const key in setupStore) {
    if (typeof setupStore[key] === "function") {
      setupStore[key] = wrapAction(setupStore[key]);
    }
  }

  // 将设置存储的结果合并到存储对象中，并将其添加到Pinia的存储集合中
  Object.assign(store, setupStore);
  pinia._s.set(id, store);
  return store;
}


/**
 * 定义一个Pinia store的函数
 *
 * 该函数接受两个参数，根据参数类型决定是使用setup函数还是options对象来定义store
 * 主要目的是为了提供一个灵活的store定义方式，同时支持通过函数或对象的方式来配置store
 *
 * @param {string | object} idOptions - store的唯一标识符或包含标识符和选项的对象
 * @param {function | object} setup - setup函数或store的选项对象
 * @returns {function} 返回一个用于创建store的函数
 */
export function defineStore(idOptions, setup) {
  // 定义store的唯一标识符和选项
  let id;
  let options;

  // 判断setup是否为函数，以便后续决定使用哪种方式创建store
  const isSetupStore = typeof setup === "function";

  // 根据传入参数的类型，分配id和options的值
  if (typeof idOptions === "string") {
    id = idOptions;
    options = setup;
  } else {
    id = idOptions.id;
    options = idOptions;
  }

  /**
   * 创建并返回一个store实例的函数
   *
   * 该函数首先获取当前的Vue实例，然后检查Pinia实例中是否已经存在该store
   * 如果不存在，则根据isSetupStore的值决定使用哪种方式创建store
   * 最后从Pinia实例中获取并返回store实例
   *
   * @returns {object} 返回store实例
   */
  function useStore() {
    // 获取当前Vue实例
    const instance = getCurrentInstance();
    // 从Vue实例中注入Pinia实例
    const pinia = instance && inject(piniaSymbol);

    // 检查Pinia实例中是否已经存在该store，如果不存在则创建
    if (!pinia._s.has(id)) {
      if (isSetupStore) {
        // 使用setup函数创建store
        createSetupStore(id, setup, pinia);
      } else {
        // 使用options对象创建store
        createOptionStore(id, options, pinia);
      }
    }

    // 从Pinia实例中获取store实例并返回
    const store = pinia._s.get(id);
    return store;
  }

  // 返回用于创建store实例的函数
  return useStore;
}

